import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter, constantRoutes, allAsyncRoutes, anyRoute } from '@/router'
import router from '@/router'
import cloneDeep from 'lodash/cloneDeep'

/*
  根据用户路由权限数据信息决定最后路由器里面注册的都是哪些路由
    (1)修改路由当中注册的路由为三种不同的路由,常量路由、异步路由和任意路由
    (2)创建路由器的时候只能注册常量路由
    (3)获取用户信息的时候,真正的根据用户权限数据信息决定路由当中有哪些异步路由
    (3)存储用户权限信息相关
        存储用户的角色信息
        存储用户的按钮权限信息
        存储用户的异步路由数组,不是路由名称
        存储用户最终的所有路由数组,不是路由名称
        通过返回的用户的路由名称数组,从所有的异步路由数组当中,过滤出用户自己的异步路由数组
    (4)设置用户路由的时候可以使用动态添加路由,把异步路由和任意路由添加到路由器的路由当中
        router.addRoutes([...routes,anyRoutes])
    (5)在生成菜单的时候,是根据最终的所有的路由数组动态生成的
      把 layout 当中的 sidbar 组件内部 routes 改成自己存储的 routes

  总结:
      路由(菜单)权限的控制
        全局路由导航守卫和动态添加路由
          全局路由导航守卫当中获取用户的菜单权限数据信息
          根据用户的菜单权限数据信息,从所有的异步路由当中过滤出当前用户自己的异步路由
          使用路由器的动态添加路由,将用户的异步路由和任意路由,动态添加到路由器当中
          菜单是根据当前用户的所有路由动态生成
*/

// 通过返回的用户异步路由名称数组,从所有的异步路由数组当中过滤出用户异步路由数组
function filterAsyncRoutes (allAsyncRoutes, routeNames) {
  const asyncRoutes = allAsyncRoutes.filter(item => {
    if (routeNames.includes(item.name)) {
      // 代表当前遍历的这一项路由,它的名称是在返回来的路由名称数组里面,那么这一项路由就要了
      if (item.children && item.children.length > 0) {
        // 如果当前这个路由是有子路由的,那么子路由也要去过滤出用户路由名称所包含的
        // 过滤出子路由替换掉原本的子路由
        item.children = filterAsyncRoutes(item.children, routeNames)
      }
      return true
    }
  })
  return asyncRoutes
}

const getDefaultState = () => {
  return {
    // 登录后的 token 信息
    token: getToken(),
    // 获取用户信息后存储用户的名称
    name: '',
    // 获取用户信息后存储用户的头像
    avatar: '',
    // 保存用户的角色权限信息
    roles: [],
    // 保存用户的按钮权限信息
    buttons: [],
    // 保存当前用户返回的 name 数组对应的异步路由数组(返回的数据是异步路由名称的数组)
    asyncRoutes: [],
    // 最终用户要使用的所有路由,包含常量路由、用户的异步路由和任意路由
    routes: []
  }
}

const state = getDefaultState()

const mutations = {
  RESET_STATE: (state) => {
    Object.assign(state, getDefaultState())
  },
  // 登录后设置用户 token
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  // 获取用户信息后设置用户信息
  SET_USERINFO (state, userInfo) {
    state.name = userInfo.name
    state.avatar = userInfo.avatar
    state.buttons = userInfo.buttons
    state.roles = userInfo.roles
  },
  SET_ROUTES (state, asyncRoutes) {
    state.asyncRoutes = asyncRoutes
    // 将常量路由和用户的异步路由以及任意路由拼接为这个用户所有的路由数组,依据此数组生成菜单
    state.routes = constantRoutes.concat(asyncRoutes, anyRoute)

    // 路由器当中目前还是只有常量路由,所以需要动态的给路由器当中添加路由
    // 动态的给路由器添加路由,参数必须是符合路由配置的数组
    router.addRoutes([...asyncRoutes, anyRoute])
  }
}

const actions = {
  // 处理登录业务
  async login ({ commit }, userInfo) {
    const { username, password } = userInfo
    const result = await login({ username: username.trim(), password: password })
    if (result.code === 20000) {
      commit('SET_TOKEN', result.data.token)
      // 存储 token
      setToken(result.data.token)
      return 'OK'
    } else {
      return Promise.reject(new Error('FAILED'))
    }
  },
  // 获取用户信息
  getInfo ({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
        const { data } = response
        if (!data) {
          return reject('Verification failed, please Login again.')
        }
        commit('SET_USERINFO', data)

        // data.routes 返回的是这个用户所有的路由权限信息,就是所有这个用户要注册的异步路由的名称 name
        /*
          allAsyncRoutes 函数会把 allAsyncRoutes 内部原本的子路由过滤掉一部分
          原本的子路由不在了,那么新用户登录之后,再次过滤的路由便是残缺的
          所以这里过滤的时候,需要把所有的异步路由做深拷贝,然后再进行操作,不能影响原有的
        */
        commit('SET_ROUTES', filterAsyncRoutes(cloneDeep(allAsyncRoutes), data.routes))

        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },

  // 退出登录
  logout ({ commit, state }) {
    return new Promise((resolve, reject) => {
      logout(state.token).then(() => {
        removeToken()
        // 重新设置路由
        resetRouter()
        commit('RESET_STATE')
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },
  // remove token
  resetToken ({ commit }) {
    return new Promise(resolve => {
      removeToken() // must remove  token  first
      commit('RESET_STATE')
      resolve()
    })
  }
}

export default {
  /*
    命名空间,如果设置为 true,那么当访问 actions、getters、mutations 时就需要加上名称前缀,如 user/login
    好处在于可以让多个模块有相同的 actions、getters、mutations 而不会造成覆盖
  */
  namespaced: true,
  state,
  mutations,
  actions
}

